/**
 * Copyright 2015 ABSir's Studio
 * 
 * All right reserved
 *
 * Create on 2015年4月8日 下午4:57:44
 */
package com.absir.appserv.system.service;

import java.io.IOException;
import java.io.Serializable;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Map;

import com.absir.appserv.system.adapter.SocketAdapter;
import com.absir.appserv.system.adapter.SocketAdapter.CallbackAdapte;
import com.absir.appserv.system.adapter.SocketAdapter.CallbackTimeout;
import com.absir.appserv.system.converter.BodyMsgPack;
import com.absir.appserv.system.converter.BodyMsgPack.CallbackMessagePack;
import com.absir.appserv.system.helper.HelperDatabind;
import com.absir.appserv.system.helper.HelperEncrypt;
import com.absir.appserv.system.master.MasterChannelResolver;
import com.absir.appserv.system.master.MasterChannelResolverImpl;
import com.absir.appserv.system.master.MasterServer;
import com.absir.appserv.system.master.MasterSessionResolver;
import com.absir.bean.basis.Base;
import com.absir.bean.core.BeanFactoryUtils;
import com.absir.bean.inject.value.Bean;
import com.absir.bean.inject.value.Inject;
import com.absir.bean.inject.value.InjectType;
import com.absir.bean.inject.value.Started;
import com.absir.bean.inject.value.Stopping;
import com.absir.bean.inject.value.Value;
import com.absir.context.core.ContextService;
import com.absir.context.core.ContextUtils;
import com.absir.core.kernel.KernelByte;
import com.absir.core.kernel.KernelLang.ObjectEntry;
import com.absir.server.in.InMethod;
import com.absir.server.in.InModel;
import com.absir.server.in.Input;
import com.absir.server.route.returned.ReturnedResolverBody;
import com.absir.server.socket.InputSocket;
import com.absir.server.socket.InputSocket.InputSocketAtt;
import com.absir.server.socket.ServerContext;
import com.absir.server.socket.SocketChannelContext;
import com.absir.server.socket.SocketReceiverContext;
import com.absir.server.socket.SocketServer;
import com.absir.server.socket.SocketServerContext;
import com.absir.server.socket.resolver.SocketChannelResolver;
import com.absir.server.socket.resolver.SocketSessionResolver;

/**
 * @author absir
 *
 */
@Base
@Bean
public class MasterService extends ContextService {

	/** ME */
	public static final MasterService ME = BeanFactoryUtils.get(MasterService.class);

	/** ip */
	@Value("server.master.ip")
	protected String ip;

	@Value("server.master.ipV6")
	protected String ipV6;

	/** port */
	@Value("server.master.port")
	protected int port = 28890;

	/** hosts */
	@Value("server.master.host")
	protected String[] hosts = null;

	/** hosts */
	@Value("server.master.host.exclude")
	protected String[] excludes = null;

	@Value("server.master.key")
	protected String key = "absir@qq.com";

	/** idMapSlaveKey */
	private Map<Serializable, String> idMapSlaveKey = new HashMap<Serializable, String>();

	/** socketServer */
	protected SocketServer socketServer;

	/** masterServer */
	protected MasterServer masterServer;

	/** masterReceiverContext */
	protected MasterReceiverContext masterReceiverContext;

	@Inject(type = InjectType.Selectable)
	protected MasterChannelResolver channelResolver;

	/** sessionResolver */
	@Inject(type = InjectType.Selectable)
	protected MasterSessionResolver sessionResolver;

	/**
	 * @author absir
	 *
	 */
	public static class MasterReceiverContext extends SocketReceiverContext {

		/** masterAdapter */
		protected SocketAdapter masterAdapter = new SocketAdapter();

		/**
		 * @param serverContext
		 */
		public MasterReceiverContext(ServerContext serverContext) {
			super(serverContext);
		}

		/**
		 * @return
		 */
		public SocketChannelResolver getSocketChannelResolver() {
			return ME.channelResolver;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see com.absir.server.socket.SocketReceiverContext#
		 * registerSocketChannelContext(com.absir.server.socket.ServerContext,
		 * java.io.Serializable, com.absir.server.socket.SocketChannelContext)
		 */
		@Override
		protected void registerSocketChannelContext(ServerContext mutilContext, Serializable id,
				SocketChannelContext socketChannelContext) {
			super.registerSocketChannelContext(mutilContext, id, socketChannelContext);
			// 注册通讯密钥
			SocketChannel socketChannel = socketChannelContext.getSocketChannel();
			byte[] secerets = KernelByte.getLengthBytes(socketChannel.hashCode());
			String validate = HelperEncrypt.encryptionMD5(MasterService.ME.getKey(), secerets);
			MasterService.ME.registerSlaveKey(id, validate, socketChannel);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * com.absir.server.socket.SocketReceiverContext#getSocketSessionResolver
		 * ()
		 */
		@Override
		public SocketSessionResolver getSocketSessionResolver() {
			return ME.sessionResolver;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * com.absir.server.socket.SocketReceiverContext#doResponse(java.nio
		 * .channels.SocketChannel, java.io.Serializable, int, byte[])
		 */
		@Override
		protected void doResponse(SocketChannel socketChannel, Serializable id, byte flag, byte[] buffer) {
			masterAdapter.receiveCallback(buffer, (byte) flag);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * com.absir.server.socket.SocketReceiverContext#input(java.lang.String,
		 * com.absir.server.in.InMethod, com.absir.server.in.InModel,
		 * com.absir.server.socket.InputSocket.InputSocketAtt,
		 * java.nio.channels.SocketChannel)
		 */
		@Override
		protected Input input(String uri, InMethod inMethod, InModel model, InputSocketAtt req, SocketChannel res) {
			InputSocketMaster input = new InputSocketMaster(model, req, res);
			ReturnedResolverBody.ME.setBodyConverter(input, BodyMsgPack.ME);
			return input;
		}

		/**
		 * @return the masterAdapter
		 */
		public SocketAdapter getMasterAdapter() {
			return masterAdapter;
		}

		/**
		 * @param socketChannel
		 * @param dataBytes
		 * @param head
		 * @param debug
		 * @param postData
		 * @param timeout
		 * @param callbackAdapte
		 */
		public void sendDataBytes(SocketChannel socketChannel, byte[] dataBytes, boolean head, boolean debug, byte[] postData,
				int timeout, CallbackAdapte callbackAdapte) {
			int callbackIndex = masterAdapter.generateCallbackIndex();
			if (callbackAdapte != null) {
				masterAdapter.putReceiveCallbacks(callbackIndex, callbackAdapte);
			}

			byte[] buffer = masterAdapter.sendDataBytes(5, dataBytes, head, debug, callbackIndex, postData);
			buffer[4] = InputSocketAtt.CALLBACK_FLAG;
			KernelByte.setLength(buffer, 5, 1);
			if (InputSocket.writeChannelBuffer(socketChannel, buffer)) {
				if (timeout > 0 && callbackIndex > masterAdapter.getMinCallbackIndex()) {
					CallbackTimeout callbackTimeout = new CallbackTimeout();
					callbackTimeout.timeout = System.currentTimeMillis() + timeout;
					callbackTimeout.socketAdapter = masterAdapter;
					callbackTimeout.callbackIndex = callbackIndex;
					SocketAdapter.addCallbackTimeout(callbackTimeout);
					ObjectEntry<CallbackAdapte, CallbackTimeout> entry = masterAdapter.getReceiveCallbacks().get(callbackIndex);
					if (entry != null) {
						entry.setValue(callbackTimeout);
					}
				}

			} else {
				masterAdapter.receiveCallback(null, (byte) 0, callbackIndex);
			}
		}

		/**
		 * @param socketChannel
		 * @param uri
		 * @param postData
		 * @param callbackMessagePack
		 * @throws IOException
		 */
		public void sendDataBytes(SocketChannel socketChannel, String uri, byte[] postData,
				CallbackMessagePack<?> callbackMessagePack) throws IOException {
			sendDataBytes(socketChannel, uri.getBytes(ContextUtils.getCharset()), true, false, postData, 30000, callbackMessagePack);
		}

		/**
		 * @param socketChannel
		 * @param uri
		 * @param postData
		 * @param callbackMessagePack
		 * @throws IOException
		 */
		public void sendData(SocketChannel socketChannel, String uri, Object postData, CallbackMessagePack<?> callbackMessagePack)
				throws IOException {
			sendDataBytes(socketChannel, uri, postData == null ? null : HelperDatabind.writeAsBytes(postData), callbackMessagePack);
		}
	}

	/**
	 * 开始服务
	 * 
	 * @throws IOException
	 */
	@Started
	protected void startService() throws IOException {
		socketServer = new SocketServer();
		if (ip == null) {
			ip = SocketServerContext.ME.getIp();
		}

		if (ipV6 == null) {
			ipV6 = SocketServerContext.ME.getIpV6();
		}

		if (channelResolver == null) {
			channelResolver = new MasterChannelResolverImpl();
		}

		if (sessionResolver == null) {
			sessionResolver = new MasterSessionResolverImpl();
		}

		sessionResolver.setSessionFilters(hosts, excludes);
		masterServer = createMasterServer();
		masterServer.setPort(port);
		masterServer.setIp(ip);
		masterServer.setIpV6(ipV6);
		ServerContext serverContext = new ServerContext();
		serverContext.setServer(masterServer);
		masterReceiverContext = new MasterReceiverContext(serverContext);
		socketServer.start(port, SocketServerContext.ME.getBacklog(), SocketServerContext.getInetAddress(ip, ipV6),
				SocketServerContext.ME.getBufferSize(), SocketServerContext.ME.getReceiveBufferSize(),
				SocketServerContext.ME.getSendBufferSize(), channelResolver, masterReceiverContext);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.absir.context.core.ContextService#step(long)
	 */
	@Override
	public void step(long contextTime) {
		if (masterReceiverContext != null) {
			masterReceiverContext.getServerContext().stepDone(contextTime);
		}
	}

	/**
	 * 关闭服务
	 */
	@Stopping
	protected void stopService() {
		socketServer.close();
	}

	/**
	 * @return
	 */
	protected SocketServer createSocketServer() {
		return new SocketServer();
	}

	/**
	 * @return
	 */
	protected MasterServer createMasterServer() {
		return new MasterServer();
	}

	/**
	 * @return the key
	 */
	public String getKey() {
		return key;
	}

	/**
	 * @param id
	 * @param key
	 * @param socketChannel
	 */
	public void registerSlaveKey(Serializable id, String key, SocketChannel socketChannel) {
		key = id + "_" + key;
		idMapSlaveKey.put(id, key);
		InputSocketMaster.writeByteBuffer(socketChannel, 2, key.getBytes());
	}

	/**
	 * @param id
	 * @return
	 */
	public String getSlaveKey(Serializable id) {
		return idMapSlaveKey.get(id);
	}

	/**
	 * @param id
	 */
	public void unregisterSlaveKey(Serializable id) {
		idMapSlaveKey.remove(id);
	}

	/**
	 * @return the masterReceiverContext
	 */
	public MasterReceiverContext getMasterReceiverContext() {
		return masterReceiverContext;
	}

	/**
	 * @return the channelResolver
	 */
	public MasterChannelResolver getChannelResolver() {
		return channelResolver;
	}

	/**
	 * @return the sessionResolver
	 */
	public MasterSessionResolver getSessionResolver() {
		return sessionResolver;
	}
}
